// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections.Generic;
using System.Linq;

namespace Tierless.Framework.EntityTypes
{
	/// <summary>
	/// Represents a collection of <see cref="EntityReference"/> instances.
	/// </summary>
	public class EntityReferenceCollection : NamedEntityObjectTypeCollection<EntityReference>
	{
		private Dictionary<string, EntityReference> _lookups;

		private void EnsureLookups()
		{
			if (_lookups == null)
				_lookups = new Dictionary<string, EntityReference>();
		}

		/// <summary>
		/// Returns the lookup reference with the given name, if it exists, otherwise, an exception is thrown.
		/// </summary>
		/// <param name="lookupName">The lookup name for the reference (i.e. EntityReference.IndexName).</param>
		/// <returns>The lookup reference with the given name, if it exists.</returns>
		/// <exception cref="ArgumentException">If no lookup reference with the given name is found.</exception>
		public EntityReference GetLookupReference(string lookupName)
		{
			var reference = FindLookupReference(lookupName);
			if (reference == null)
				throw new ArgumentException(String.Format("Lookup reference '{0}' not found.", lookupName));

			return reference;
		}

		/// <summary>
		/// Returns the lookup reference with the given name, if it exists, null otherwise.
		/// </summary>
		/// <param name="lookupName">The lookup name for the reference (i.e. EntityReference.IndexName).</param>
		/// <returns>The lookup reference with the given name, if it exists.</returns>
		public EntityReference FindLookupReference(string lookupName)
		{
			EnsureLookups();

			EntityReference reference;
			_lookups.TryGetValue(lookupName, out reference);

			return reference;
		}

		/// <summary>
		/// Returns the unique lookup reference that targets the given entity type, if there is one, null otherwise.
		/// </summary>
		/// <param name="targetEntityTypeName">The name of the target entity type.</param>
		/// <returns>The unique lookup reference that targets the given entity type.</returns>
		/// <remarks>
		/// If this collection contains one, and only one, reference that targets the given entity type, then it will be
		/// returned. Otherwise, null is returned.
		/// </remarks>
		public EntityReference FindLookupReferenceByTarget(string targetEntityTypeName)
		{
			EntityReference result = null;

			foreach (var reference in this.Where(AReference => AReference.TargetEntityTypeName == targetEntityTypeName))
			{
				if (result != null)
				{
					result = null;
					break;
				}
				else
					result = reference;
			}

			return result;
		}

		private Dictionary<string, EntityReference> _children;

		private void EnsureChildren()
		{
			if (_children == null)
				_children = new Dictionary<string, EntityReference>();
		}

		/// <summary>
		/// Gets the child reference with the given name, if it exists, otherwise an exception is thrown.
		/// </summary>
		/// <param name="childName">The child name for the reference (i.e. EntityReference.IndexName).</param>
		/// <returns>The child reference with the given name, if it exists.</returns>
		/// <exception cref="ArgumentException">If no child reference with the given name is found.</exception>
		public EntityReference GetChildReference(string childName)
		{
			var reference = FindChildReference(childName);
			if (reference == null)
				throw new ArgumentException(String.Format("Child reference '{0}' not found.", childName));

			return reference;
		}

		/// <summary>
		/// Gets the child reference with the given name, if it exists, null otherwise.
		/// </summary>
		/// <param name="childName">The child name for the reference (i.e. EntityReference.IndexName).</param>
		/// <returns>The child reference with the given name, if it exists.</returns>
		public EntityReference FindChildReference(string childName)
		{
			EnsureChildren();

			EntityReference reference;
			_children.TryGetValue(childName, out reference);

			return reference;
		}

		/// <summary>
		/// Returns the unique reference that originates in the given source entity type, if there is one, null otherwise.
		/// </summary>
		/// <param name="sourceEntityTypeName">The name of the source entity type.</param>
		/// <returns>The unique reference that originates in the given source entity type, if there is one, null otherwise.</returns>
		/// <remarks>
		/// If the collection contains one and only one reference that originates in the given entity type, it is returned,
		/// otherwise, null is returned.
		/// </remarks>
		public EntityReference FindChildReferenceBySource(string sourceEntityTypeName)
		{
			EntityReference result = null;

			foreach (var reference in this.Where(AReference => AReference.SourceEntityTypeName == sourceEntityTypeName))
			{
				if (result != null)
				{
					result = null;
					break;
				}
				else
					result = reference;
			}

			return result;
		}

		private void IndexItem(EntityReference item)
		{
			if (item.Kind == ReferenceKind.Child)
			{
				EnsureChildren();

				if (!_children.ContainsKey(item.IndexName))
					_children.Add(item.IndexName, item);
			}
			else
			{
				EnsureLookups();
				_lookups.Add(item.IndexName, item);
			}
		}

		private void UnindexItem(EntityReference item)
		{
			if (item.Kind == ReferenceKind.Child)
			{
				EnsureChildren();
				_children.Remove(item.IndexName);
			}
			else
			{
				EnsureLookups();
				_lookups.Remove(item.IndexName);
			}
		}

		protected override void InsertItem(int index, EntityReference item)
		{
			base.InsertItem(index, item);
			IndexItem(item);
		}

		protected override void RemoveItem(int index)
		{
			UnindexItem(this[index]);
			base.RemoveItem(index);
		}

		protected override void SetItem(int index, EntityReference item)
		{
			UnindexItem(this[index]);
			base.SetItem(index, item);
			IndexItem(item);
		}
	}
}